#include "value.h"
#include "../string/json.h"
#include <stdlib.h>
#include <stdio.h>
#include <stack>

namespace jlib {

struct JsonParserProxy : public json::Proxy {

  virtual bool intoDict () noexcept {
    auto node = this->add(value::type_t::Map);
    this->stack.push(node);
    return false;
  }

  virtual bool exitDict () noexcept {
    this->stack.pop();
    return false;
  }

  virtual bool intoList () noexcept {
    auto node = this->add(value::type_t::Arr);
    this->stack.push(node);
    return false;
  }

  virtual bool exitList () noexcept {
    this->stack.pop();
    return false;
  }

  virtual bool onMore () noexcept {
    return false;
  }

  virtual bool onPair () noexcept {
    return false;
  }

  virtual bool onNull () noexcept {
    this->add(value::type_t::Nil);
    return false;
  }

  virtual bool onInteger (long v) noexcept {
    this->add(v);
    return false;
  }

  virtual bool onFloat (double v) noexcept {
    this->add(v);
    return false;
  }

  virtual bool onBoolean (bool v) noexcept {
    this->add(v);
    return false;
  }

  virtual bool onString (const char *s, const char *e) noexcept {
    this->add(s, e - s);
    return false;
  }

  virtual bool onKey (const char *s, const char *e) noexcept {
    this->key_start = s;
    this->key_length = e - s;
    return false;
  }

  virtual void onError (const char *i) noexcept {
    this->result.clear();
  }

  template<typename... Args>
  value *add (Args&&... args) noexcept {
    if (this->stack.empty()) {
      this->result = value(std::forward<Args>(args)...);
      return &this->result;
    }
    auto top = this->stack.top();
    if (top->isMap()) {
      auto &map = top->getMap();
      auto iter = map.assign(value::str_t(this->key_start, this->key_length), std::forward<Args>(args)...);
      return &iter->value;
    }
    else {
      auto &arr = top->getArr();
      arr.emplace_back(std::forward<Args>(args)...);
      return &arr.back();
    }
  }

public:
  value result;
  std::stack<value*> stack;
  const char *key_start;
  size_t key_length;
};


value value::fromJson (const char *json, const char **end) noexcept {
  if (!json)
    return value();
  JsonParserProxy proxy;
  auto i = json::parse(json, proxy);
  if (end)
    *end = proxy.result ? i : nullptr;
  return proxy.result;
}


value::bit_t value::nil_as_bit (const value *thiz) noexcept {
  return false;
}
value::int_t value::nil_as_int (const value *thiz) noexcept {
  return 0L;
}
value::flt_t value::nil_as_flt (const value *thiz) noexcept {
  return 0.0;
}
value::str_t value::nil_as_str (const value *thiz) noexcept {
  return value::str_t();
}
value::arr_t value::nil_as_arr (const value *thiz) noexcept {
  return arr_t();
}
value::map_t value::nil_as_map (const value *thiz) noexcept {
  return map_t();
}
void value::nil_to_json (const value *thiz, str_t &dst) noexcept {
  dst += "null";
}
void value::nil_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  dst += "null";
}


value::bit_t value::bit_as_bit (const value *thiz) noexcept {
  return thiz->data.b;
}
value::int_t value::bit_as_int (const value *thiz) noexcept {
  return thiz->data.b ? 1L : 0L;
}
value::flt_t value::bit_as_flt (const value *thiz) noexcept {
  return thiz->data.b ? 1.0 : 0.0;
}
value::str_t value::value::bit_as_str (const value *thiz) noexcept {
  return thiz->data.b ? "true" : "false";
}
value::arr_t value::bit_as_arr (const value *thiz) noexcept {
  return value::arr_t();
}
value::map_t value::bit_as_map (const value *thiz) noexcept {
  return value::map_t();
}
void value::bit_to_json (const value *thiz, str_t &dst) noexcept {
  dst += thiz->data.b ? "true" : "false";
}
void value::bit_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  dst += thiz->data.b ? "true" : "false";
}


value::bit_t value::int_as_bit (const value *thiz) noexcept {
  return thiz->data.i != 0L;
}
value::int_t value::int_as_int (const value *thiz) noexcept {
  return thiz->data.i;
}
value::flt_t value::int_as_flt (const value *thiz) noexcept {
  return thiz->data.i;
}
value::str_t value::int_as_str (const value *thiz) noexcept {
  return std::to_string(thiz->data.i);
}
value::arr_t value::int_as_arr (const value *thiz) noexcept {
  return value::arr_t();
}
value::map_t value::int_as_map (const value *thiz) noexcept {
  return value::map_t();
}
void value::int_to_json (const value *thiz, str_t &dst) noexcept {
  char buf[64];
  int size = sprintf(buf, "%ld", thiz->data.i);
  buf[size] = 0;
  dst += buf;
}
void value::int_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  char buf[64];
  int size = sprintf(buf, "%ld", thiz->data.i);
  buf[size] = 0;
  dst += buf;
}


value::bit_t value::flt_as_bit (const value *thiz) noexcept {
  return thiz->data.f != 0.0;
}
value::int_t value::flt_as_int (const value *thiz) noexcept {
  return (value::int_t)thiz->data.f;
}
value::flt_t value::flt_as_flt (const value *thiz) noexcept {
  return thiz->data.f;
}
value::str_t value::flt_as_str (const value *thiz) noexcept {
  return std::to_string(thiz->data.f);
}
value::arr_t value::flt_as_arr (const value *thiz) noexcept {
  return value::arr_t();
}
value::map_t value::flt_as_map (const value *thiz) noexcept {
  return value::map_t();
}
void value::flt_to_json (const value *thiz, str_t &dst) noexcept {
  char buf[128];
  int size = sprintf(buf, "%lf", thiz->data.f);
  buf[size] = 0;
  dst += buf;
}
void value::flt_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  char buf[128];
  int size = sprintf(buf, "%lf", thiz->data.f);
  buf[size] = 0;
  dst += buf;
}


value::bit_t value::str_as_bit (const value *thiz) noexcept {
  return thiz->data.s->length() > 0;
}
value::int_t value::str_as_int (const value *thiz) noexcept {
  return atol(thiz->data.s->c_str());
}
value::flt_t value::str_as_flt (const value *thiz) noexcept {
  return atof(thiz->data.s->c_str());
}
value::str_t value::str_as_str (const value *thiz) noexcept {
  return *thiz->data.s;
}
value::arr_t value::str_as_arr (const value *thiz) noexcept {
  value::arr_t arr;
  char buf[4];
  const char *s = thiz->data.s->c_str();
  for (char c = *s; c; ++s) {
    size_t i = 0;
    buf[i++] = c;
    if (c < 0) {
      buf[i++] = *++s;
      if (c & 0x20)
        buf[i++] = *++s;
    }
    arr.push_back(value(buf, i));
  }
  return arr;
}
value::map_t value::str_as_map (const value *thiz) noexcept {
  return value::map_t();
}
void value::str_to_json (const value *thiz, str_t &dst) noexcept {
  dst += '"';
  dst += *thiz->data.s;
  dst += '"';
}
void value::str_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  dst += '"';
  dst += *thiz->data.s;
  dst += '"';
}


value::bit_t value::arr_as_bit (const value *thiz) noexcept {
  return thiz->data.a->size() > 0;
}
value::int_t value::arr_as_int (const value *thiz) noexcept {
  return thiz->data.a->size();
}
value::flt_t value::arr_as_flt (const value *thiz) noexcept {
  return thiz->data.a->size();
}
value::str_t value::arr_as_str (const value *thiz) noexcept {
  return "";
}
value::arr_t value::arr_as_arr (const value *thiz) noexcept {
  return *thiz->data.a;
}
value::map_t value::arr_as_map (const value *thiz) noexcept {
  value::map_t map;
  const value::arr_t &arr = *thiz->data.a;
  for (size_t i = 0, n = arr.size(); i < n; ++i)
    map.insert(std::to_string(i), arr[i]);
  return map;
}
void value::arr_to_json (const value *thiz, str_t &dst) noexcept {
  auto &arr = *thiz->data.a;
  if (arr.empty()) {
    dst += "[]";
    return;
  }
  dst += '[';
  for (size_t i = 0, n = arr.size(); ;) {
    auto &value = arr[i];
    value.func->toJson(&value, dst);
    if (++i >= n)
      break;
    dst += ',';
  }
  dst += ']';
}
void value::arr_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  auto &arr = *thiz->data.a;
  if (arr.empty()) {
    dst += "[]";
    return;
  }
  size_t breakLine = cfg.indentSpaces > 0 ? 1 : 0;
  indents += cfg.indentSpaces;
  dst += '[';
  dst.append(breakLine, '\n');
  dst.append(indents, ' ');
  for (size_t i = 0, n = arr.size(); ;) {
    auto &value = arr[i];
    value.func->toString(&value, dst, indents, cfg);
    if (++i >= n)
      break;
    dst.append(cfg.commaLSpaces, ' ');
    dst += ',';
    dst.append(cfg.commaRSpaces, ' ');
    dst.append(breakLine, '\n');
    dst.append(indents, ' ');
  }
  indents -= cfg.indentSpaces;
  dst.append(breakLine, '\n');
  dst.append(indents, ' ');
  dst += ']';
}


value::bit_t value::map_as_bit (const value *thiz) noexcept {
  return thiz->data.m->size() > 0;
}
value::int_t value::map_as_int (const value *thiz) noexcept {
  return thiz->data.m->size();
}
value::flt_t value::map_as_flt (const value *thiz) noexcept {
  return thiz->data.m->size();
}
value::str_t value::map_as_str (const value *thiz) noexcept {
  return "";
}
value::arr_t value::map_as_arr (const value *thiz) noexcept {
  arr_t arr;
  auto &map = *thiz->data.m;
  for (const auto &node : map)
    arr.push_back(node.value);
  return arr;
}
value::map_t value::map_as_map (const value *thiz) noexcept {
  return *thiz->data.m;
}
void value::map_to_json (const value *thiz, str_t &dst) noexcept {
  auto &map = *thiz->data.m;
  if (map.empty()) {
    dst += "{}";
    return;
  }
  dst += '{';
  auto i = map.cbegin();
  auto e = map.cend();
  while (true) {
    auto &key = i->key;
    auto &value = i->value;
    dst += '"';
    dst += key;
    dst += "\":";
    value.func->toJson(&value, dst);
    if (++i == e)
      break;
    dst += ',';
  }
  dst += '}';
}
void value::map_to_string (const value *thiz, str_t &dst, size_t &indents, const fmt_t &cfg) noexcept {
  auto &map = *thiz->data.m;
  if (map.empty()) {
    dst += "{}";
    return;
  }
  size_t breakLine = cfg.indentSpaces > 0 ? 1 : 0;
  indents += cfg.indentSpaces;
  dst += '{';
  dst.append(breakLine, '\n');
  dst.append(indents, ' ');
  auto i = map.cbegin();
  auto e = map.cend();
  while (true) {
    auto &key = i->key;
    auto &value = i->value;
    dst += '"';
    dst += key;
    dst += '"';
    dst.append(cfg.colonLSpaces, ' ');
    dst += ':';
    dst.append(cfg.colonRSpaces, ' ');
    value.func->toString(&value, dst, indents, cfg);
    if (++i == e)
      break;
    dst.append(cfg.commaLSpaces, ' ');
    dst += ',';
    dst.append(cfg.commaRSpaces, ' ');
    dst.append(breakLine, '\n');
    dst.append(indents, ' ');
  }
  indents -= cfg.indentSpaces;
  dst.append(breakLine, '\n');
  dst.append(indents, ' ');
  dst += '}';
}


value::func_t value::vtables[] = {
  {},
  { &value::nil_as_bit, &value::nil_as_int, &value::nil_as_flt, &value::nil_as_str, &value::nil_as_arr, &value::nil_as_map, &value::nil_to_json, &value::nil_to_string },
  { &value::bit_as_bit, &value::bit_as_int, &value::bit_as_flt, &value::bit_as_str, &value::bit_as_arr, &value::bit_as_map, &value::bit_to_json, &value::bit_to_string },
  { &value::int_as_bit, &value::int_as_int, &value::int_as_flt, &value::int_as_str, &value::int_as_arr, &value::int_as_map, &value::int_to_json, &value::int_to_string },
  { &value::flt_as_bit, &value::flt_as_int, &value::flt_as_flt, &value::flt_as_str, &value::flt_as_arr, &value::flt_as_map, &value::flt_to_json, &value::flt_to_string },
  { &value::str_as_bit, &value::str_as_int, &value::str_as_flt, &value::str_as_str, &value::str_as_arr, &value::str_as_map, &value::str_to_json, &value::str_to_string },
  { &value::arr_as_bit, &value::arr_as_int, &value::arr_as_flt, &value::arr_as_str, &value::arr_as_arr, &value::arr_as_map, &value::arr_to_json, &value::arr_to_string },
  { &value::map_as_bit, &value::map_as_int, &value::map_as_flt, &value::map_as_str, &value::map_as_arr, &value::map_as_map, &value::map_to_json, &value::map_to_string }
};


} // namespace jlib